#!/usr/bin/env bash
#
#  Licensed to the Apache Software Foundation (ASF) under one or more
#  contributor license agreements.  See the NOTICE file distributed with
#  this work for additional information regarding copyright ownership.
#  The ASF licenses this file to You under the Apache License, Version 2.0
#  (the "License"); you may not use this file except in compliance with
#  the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#

#
# common and unlikely to change vars
#

# the upstream apache git repo for apache metron
METRON_UPSTREAM="https://gitbox.apache.org/repos/asf/metron.git"
# the upstream apache git repo for apache metron bro plugin kafka
BRO_PLUGIN_UPSTREAM="https://gitbox.apache.org/repos/asf/metron-bro-plugin-kafka.git"
# the common configuration file with the committer info
CONFIG_FILE=~/.metron-prepare-commit

GITHUB_REMOTE="origin"
BASE_BRANCH=master

# other var setup by these functions
PR=
WORK=
ORIGIN=
UPSTREAM=
PR_BRANCH=
USER=
EMAIL=
JIRA=
DESC=
APACHE_NAME=
APACHE_EMAIL=
GITHUB_NAME=
CHOSEN_REPO=

#
# Initialize the variables from the default configuration file, if it exists
#
function init_configuration {
    # does a config file already exist?
    echo "$CONFIG_FILE"
    if [[ -f ${CONFIG_FILE} ]]; then
        #shellcheck source=/dev/null
        source ${CONFIG_FILE}
        echo "  ...using settings from $CONFIG_FILE"
    fi
}


#
# Initialize the committer variables if they are not provided through the configuration file.
# If it is not present, it will be written out for the next time
#
function init_committer_info {
    # github account of committer (you)
    if [[ -z "$GITHUB_NAME" ]]; then
        read -p "  your github username [$GITHUB_NAME]: " INPUT
        [[ -n "$INPUT" ]] && GITHUB_NAME=${INPUT}

        # write setting to config file
        echo "GITHUB_NAME=$GITHUB_NAME" >> ${CONFIG_FILE}
    fi

    # apache id of committer (you)
    if [[ -z "$APACHE_NAME" ]]; then
      read -p "  your apache userid [$APACHE_NAME]: " INPUT
      [[ -n "$INPUT" ]] && APACHE_NAME=${INPUT}

      # write setting to config file
      echo "APACHE_NAME=$APACHE_NAME" >> ${CONFIG_FILE}
    fi

    # apache email addr of committer (you)
    if [[ -z "$APACHE_EMAIL" ]]; then
      APACHE_EMAIL=${APACHE_NAME}@apache.org
      read -p "  your apache email [$APACHE_EMAIL]: " INPUT
      [[ -n "$INPUT" ]] && APACHE_EMAIL=${INPUT}

      # write setting to config file, so it is not needed next time
      echo "APACHE_EMAIL=$APACHE_EMAIL" >> ${CONFIG_FILE}
    fi
}

#
# Provide the user with a choice of the metron or bro repositories
#
function choose_metron_or_bro_repo {
    # which repo?  metron or metron-bro-plugin-kafka
    echo "    [1] metron"
    echo "    [2] metron-bro-plugin-kafka"
    read -p "  which repo? [1]: " INPUT
    case "${INPUT}" in
      [Bb][Rr][Oo]|[Mm][Ee][Tt][Rr][Oo][Nn]-[Bb][Rr][Oo]-[Pp][Ll][Uu][Gg][Ii][Nn]-[Kk][Aa][Ff][Kk][Aa]|*metron-bro-plugin-kafka\.git|2)
        INPUT="${BRO_PLUGIN_UPSTREAM}" ;;
      [Mm][Ee][Tt][Rr][Oo][Nn]|*metron\.git|1|'')
        INPUT="${METRON_UPSTREAM}" ;;
      *)
        echo "Invalid repo, provided \"${INPUT}\".  Please choose between metron or metron-bro-plugin-kafka"
        exit 1
        ;;
    esac
    [[ -n "$INPUT" ]] && UPSTREAM=${INPUT}

    CHOSEN_REPO=$(basename ${UPSTREAM%%.git})
}

#
# Ask the user for the PR number
#
function read_pull_request {
    # retrieve the pull request identifier
    read -p "  pull request: " PR
    if [[ -z "$PR" ]]; then
      echo "Error: missing pr"
      exit 1
    fi

    # ensure that the pull request exists
    PR_STATE=$(curl -s https://api.github.com/repos/apache/${CHOSEN_REPO}/pulls/${PR} | jq ".state")
    PR_MERGEABLE=$(curl -s https://api.github.com/repos/apache/${CHOSEN_REPO}/pulls/${PR} | jq ".mergeable_state")
    if [[ "$PR_STATE" != "\"open\"" ]]; then
      echo "Error: pull request #$PR is not open or does not exist"
      exit 1
    fi

    if [[ "$PR_MERGEABLE" != "\"clean\"" ]] && [[ "$PR_MERGEABLE" != "\"unstable\"" ]]; then
      echo "Error: pull request #$PR is not mergable. mergeable_state is $PR_MERGEABLE"
      exit 1
    fi
}

#
# Setup the working directory.
# It is possible to override the default directory name by passing
# the desired directory name, using the available global variables
#
function setup_working_directory {
    # working directory
    if [[ -z $1 ]]; then
        WORK=~/tmp/${CHOSEN_REPO}-pr${PR}
    else
        WORK=$1
    fi

    read -p "  local working directory [$WORK]: " INPUT
    [[ -n "$INPUT" ]] && WORK=${INPUT}

    # handle tilde expansion
    WORK="${WORK/#\~/$HOME}"

    # warn the user if the working directory exists
    if [[ -d "$WORK" ]]; then
      read -p "  directory exists [$WORK].  continue merge on existing repo? [yN] " -n 1 -r
      echo
      if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        exit 1
      fi
    fi
}

#
# If required will clone the desired repo.  The default repo is metron, but an override
# may be passed as a parameter.
# If the directory already exists, it will ask the user for the name of the remote to use, with the
# default being "origin"
#
function setup_code {
    # if working directory does not exist, checkout the base branch
    if [[ ! -d "$WORK" ]]; then

        REPO_NAME="metron"
        if [[ -n $1 ]]; then
            REPO_NAME=$1
        fi

        # origin repository
        ORIGIN="https://github.com/apache/${REPO_NAME}"
        read -p "  origin repo [$ORIGIN]: " INPUT
        [[ -n "$INPUT" ]] && ORIGIN=${INPUT}

        # what branch did the PR get submitted against?  could be a feature branch
        BASE_BRANCH=$(curl -s https://api.github.com/repos/apache/${REPO_NAME}/pulls/${PR} | python -c 'import sys, json; print json.load(sys.stdin)["base"]["ref"]')
        read -p "  base branch to merge into [$BASE_BRANCH]: " INPUT
        [[ -n "$INPUT" ]] && BASE_BRANCH=${INPUT}

        # clone the repository and fetch updates
        mkdir -p ${WORK}
        git clone ${ORIGIN} ${WORK}
        cd ${WORK} &> /dev/null || { echo "failed to change directory to ${WORK}" ; exit 1 ;}

        # setup the git user and email for your apache account
        git config user.name "$APACHE_NAME"
        git config user.email "$APACHE_EMAIL"

        # fetch any changes from upstream
        git remote add upstream ${UPSTREAM}
        if git fetch upstream "$BASE_BRANCH"; then

            if [[ ${BASE_BRANCH} = "master" ]]; then
                # merge any changes from upstream
                git checkout ${BASE_BRANCH}
                git merge upstream/${BASE_BRANCH}

            else
                # create a local branch from the remote feature branch
                git checkout -B ${BASE_BRANCH} upstream/${BASE_BRANCH}

            fi

          else
            # unable to fetch the base branch
            exit $?
          fi

    else

      # if the repo already exists, allow the user to provide the name of the Github remote
      # this is needed to checkout the code for the PR
      read -p "  name of github remote [$GITHUB_REMOTE]: " INPUT
      [[ -n "$INPUT" ]] && GITHUB_REMOTE=${INPUT}

    fi

    PR_BRANCH_REF="pull/$PR/head:pr-$PR"
    PR_BRANCH="pr-$PR"
    cd ${WORK} &> /dev/null || { echo "failed to change directory to ${WORK}" ; exit 1 ;}
    git fetch ${GITHUB_REMOTE} ${PR_BRANCH_REF}
    echo ""
}

#
# Populates the contributor information from the PR information
#
function get_contributor_info {
    # use github api to retrieve the contributor's login
    USER=$(curl -s https://api.github.com/repos/apache/${CHOSEN_REPO}/pulls/${PR} | grep login | head -1 | awk -F":" '{print $2}' | sed 's/[^a-zA-Z0-9.@_-]//g')
    read -p "  github contributor's username [$USER]: " INPUT
    [[ -n "$INPUT" ]] && USER=${INPUT}

    # validate the github contributor
    if [[ -z "$USER" ]]; then
      echo "Error: missing username"
      exit 1
    fi

    # retrieve the contributor's email from the git commit history
    EMAIL=$(git log ${PR_BRANCH} | grep Author | head -1 | awk -F"<" '{print $2}' | sed 's/[<>]//g')
    read -p "  github contributor's email [$EMAIL]: " INPUT
    [[ -n "$INPUT" ]] && EMAIL=${INPUT}

    # validate email
    if [[ -z "$EMAIL" ]] || [[ "$EMAIL" = "null" ]]; then
      echo "Error: missing email"
      exit 1
    fi
}

#
# Populate the information for the JIRA associated with the PR
#
function get_jira_info {
    # can we extract the JIRA from the PR title?
    JIRA=$(curl -s https://api.github.com/repos/apache/${CHOSEN_REPO}/pulls/${PR} | grep title | head -1 | egrep -o -i 'METRON-[0-9]+' | awk '{print toupper($0)}')
    read -p "  issue identifier in jira [$JIRA]: " INPUT
    [[ -n "$INPUT" ]] && JIRA=${INPUT}

    # validate the JIRA issue
    if [[ -z "$JIRA" ]]; then
      echo "Error: missing jira"
      exit 1
    fi

    # attempt to use the jira api to get a description of the jira
    DESC=$(curl -s https://issues.apache.org/jira/si/jira.issueviews:issue-xml/${JIRA}/${JIRA}.xml | grep "<summary>" | sed 's/^.*<summary>//' | sed 's/<.summary>.*$//')
    read -p "  issue description [$DESC]: " INPUT
    [[ -n "$INPUT" ]] && DESC=${INPUT}

    # validate description
    if [[ -z "$DESC" ]]; then
      echo "Error: missing description"
      exit 1
    fi
}

#
# Merge and Commit
#
function commit {
    # commit message
    AUTHOR="$USER <$EMAIL>"
    if [[ "$USER" == "$GITHUB_NAME" ]]; then
      MSG="$JIRA $DESC ($USER) closes apache/${CHOSEN_REPO}#$PR"
    else
      MSG="$JIRA $DESC ($USER via $GITHUB_NAME) closes apache/${CHOSEN_REPO}#$PR"
    fi
    read -p "  commit message [$MSG]: " INPUT
    [[ -n "$INPUT" ]] && MSG=${INPUT}

    # merge the contributor's branch and commit
    echo ""
    if git merge --squash "$PR_BRANCH"; then
      git commit --author="$AUTHOR" -a -m "$MSG"
    else
      exit $?
    fi
}

#
# Displays the commit info ( the diff and log )
#
function review_commit_info {
    # review the commit
    git diff --stat --color "upstream/$BASE_BRANCH..$BASE_BRANCH"
    echo ""
    echo ""
    git log --oneline "$BASE_BRANCH" "^upstream/$BASE_BRANCH"
}

#
# Runs the metron unit, integration and metron-interface tests
#
function run_tests {
    read -p "  run test suite? [yN] " -n 1 -r
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]; then
      if [[ "${UPSTREAM}" == "${METRON_UPSTREAM}" ]]; then
        clean_metron_src

        run_mvn_install

        run_mvn_unit_tests

        run_mvn_integration_tests

        run_mvn_ui_tests

        run_mvn_build_rpms

        verify_licenses

      elif [[ "${UPSTREAM}" == "${BRO_PLUGIN_UPSTREAM}" ]]; then
        echo "  please verify that no bro docker containers are running before continuing,"
        read -p "  no bro docker containers are running, ready to proceed [yN] " -n 1 -r
        echo
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            run_bro_docker
            finish_bro_docker
        else
            echo "  when you are ready and the containers are stopped, please cd into the docker"
            echo "  directory and execute the run_end_to_end.sh script"
        fi
      fi
    fi

}

#
# Cleans the metron src and rpms
#
function clean_metron_src {

    mvn -q clean

    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED TO CLEAN TOP LEVEL MVN PROJECT"
        exit ${rc}
    fi

    cd metron-deployment &> /dev/null || { echo "failed to change directory to metron-deployment" ; exit 1 ;}

    mvn -q clean -Pbuild-rpms

    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED TO CLEAN RPMS"
        exit ${rc}
    fi

    cd .. &> /dev/null || { echo "failed to change directory to metron source root" ; exit 1 ;}
}

#
# runs the mvn install
#
function run_mvn_install {
    mvn -q -T 2C -DskipTests clean install
    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED mvn install"
        exit ${rc}
    fi
}

#
# runs the mvn unit tests
#
function run_mvn_unit_tests {
    mvn -q -T 2C surefire:test@unit-tests
    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED mvn unit tests"
        exit ${rc}
    fi
}

#
# runs the mvn integration tests
#
function run_mvn_integration_tests {
    mvn -q surefire:test@integration-tests
    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED mvn integration-tests"
        exit ${rc}
    fi
}

#
# runs the mvn ui related tests
#
function run_mvn_ui_tests {
    mvn -q test --projects metron-interface/metron-config,metron-interface/metron-alerts
    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED mvn tests for metron-config and metron-alerts"
        exit ${rc}
    fi


}

#
# runs the mvn rpm build
#
function run_mvn_build_rpms {
    cd metron-deployment &> /dev/null || { echo "failed to change directory to metron-deployment" ; exit 1 ; }

    mvn -q package -Pbuild-rpms

    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED mvn build-rpms"
        exit ${rc}
    fi
}

#
#  runs the bro kafka plugin's docker based tests
#
function run_bro_docker {
    cd docker &> /dev/null || { echo "failed to change directory to docker" ; exit 1; }
    ./run_end_to_end.sh

    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED run_end_to_end"
        # do NOT exit here
    fi
    cd .. &> /dev/null || { echo "failed to change directory to plugin root"; exit 1; }
}

#
#   runs the finish bro docker script to cleanup
#
function finish_bro_docker {
    cd docker &> /dev/null || { echo "failed to change directory to docker"; exit 1; }
    ./finish_end_to_end.sh

    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED finish_end_to_end"
        exit ${rc}
    fi
    cd .. &> /dev/null || { echo "failed to change directory to plugin root";
        exit 1; }
}

#
# verifies licenses using our script
#
function verify_licenses {
    dev-utilities/build-utils/verify_licenses.sh

    rc=$?; if [[ ${rc} != 0 ]]; then
        echo "ERROR> FAILED license verification"
        exit ${rc}
    fi
}

#
# Gives the user instruction on next steps
#
function please_review_then {
    echo ""
    echo "Review commit carefully then run..."
    echo "    cd $WORK"
    echo "    git push upstream $BASE_BRANCH"
    echo ""
}
